#!/usr/bin/env python
# -*- coding: utf-8 -*-

import logging
import re
import subprocess

# Find the best implementation available on this platform
try:
    from cStringIO import StringIO
except:
    from StringIO import StringIO

__all__ = ['submit', 'status']

log = logging.getLogger("glite.transfer")

status_job_final       = frozenset(['Finished', 'FinishedDirty', 'Canceled', 'Failed', 'Hold'])
status_job_processing  = frozenset(['Submitted', 'Pending', 'Ready', 'Active', 'Done', 'DoneWithErrors', 'Finishing'])
status_job             = status_job_final | status_job_processing

status_file_final      = frozenset(['Finished', 'Canceled', 'Failed', 'Hold'])
status_file_processing = frozenset(['Submitted', 'Pending', 'Ready', 'Active', 'Waiting', 'Done', 'Finishing'])
status_file            = status_file_final | status_file_processing


_re_jobid = re.compile("^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$")

def submit(items, endpoint=None):
    """
    Execute the glite-transfer-submit command and return the job ID.
    Example: 1e57e802-3d33-11e2-8837-d236cbcd303f
    
    Items can be a tuple or a tuple of tuples. Each inner tuple can be:
     - 2 string fields: (source, destination)
     - 3 string fields: (source, destination, checksum)
    
    @type  endpoint: URL string
    @param endpoint: URL of the transfer service to be used.
    @type  items: A tuple, or a tuple of tuples.
    @param items: Set of files to be transferred
    
    @rtype:  string
    @return: An hexadecimal string representing the job ID.
    """
    
    command = ['glite-transfer-submit', '-f', '-']
    
    if endpoint:
        command.extend(('-s', endpoint))
    
    log.debug("Submitting FTS job:\n%s", " ".join(command))
    p = subprocess.Popen(command, shell=False, stdin  = subprocess.PIPE,
                                               stdout = subprocess.PIPE,
                                               stderr = subprocess.PIPE)
    # Transforms this:
    #     ( ('src1', 'dst1'), ('src2', 'dst2', 'alg2:chk2'), ...)
    # Into this: 
    #     src1 dst1
    #     src2 dst2 alg2:chk2
    #     [...]
    
    if not isinstance(items[0], tuple):
        items = (items,)
    
    stdin = StringIO()
    for item in items:
        if len(item) < 3:
            stdin.write("%s %s\n"    % item)
        else:
            stdin.write("%s %s %s\n" % item)
    
    log.debug("Feeding input...")
    (out, err) = p.communicate(input = stdin.getvalue())
    log.debug("Command finished.")
    
    p.stdout.close()
    p.stderr.close()
    
    if p.returncode > 0:
        msg = "glite-transfer-submit failed with return code %s. Error message:\n%s" % (p.returncode, err)
        log.error(msg)
        raise RuntimeError(msg)
    
    if p.returncode < 0:
        msg = "glite-transfer-submit was killed by signal %d." % (-p.returncode)
        log.error(msg)
        raise RuntimeError(msg)
    
    jobid = out.strip()
    if not _re_jobid.match(jobid):
        msg = "An unknown error ocurred. Returned Job ID is not a valid one (%s)" % jobid
        log.error(msg)
        raise RuntimeError(msg)
    
    log.debug("Submitted FTS job with id %s.", jobid)
    return jobid

def status(jobids, endpoint=None):
    """
    Execute the glite-transfer-status command and return the job status information.
    
    @type  endpoint: URL string
    @param endpoint: URL of the transfer service to be used.
    @type  jobids: string or tuple of strings.
    @param jobid: The list of jobids to query its status.
    
    @rtype:  dict
    @return: A mapping structure containing the status of each job requested.
    """
    
    command = ['glite-transfer-status', '--verbose', '-l']
    
    if endpoint:
        command.extend(('-s', endpoint))
    
    if isinstance(jobids, basestring):
        jobids = (jobids,)
    
    command.extend(jobids)
    
    log.debug("Checking FTS job status:\n%s", " ".join(command))
    p = subprocess.Popen(command, shell=False, stdin  = None,
                                               stdout = subprocess.PIPE,
                                               stderr = subprocess.PIPE)
    
    (out, err) = p.communicate()
    log.debug("Command finished.")
    
    p.stdout.close()
    p.stderr.close()
    
    if p.returncode > 0:
        msg = "glite-transfer-status failed with return code %s. Error message:\n%s" % (p.returncode, err)
        log.error(msg)
        raise RuntimeError(msg)
    
    if p.returncode < 0:
        msg = "glite-transfer-status was killed by signal %d." % (-p.returncode)
        log.error(msg)
        raise RuntimeError(msg)
    
    # Split in lines, split in fields, and strip every component.
    out = [ [item[0].strip(), item[1].strip()] for item in [ line.strip().split(":", 1) for line in out.strip().split("\n") if line ] ]
    
    status     = {}
    job_status = None
    section    = []
    for item in out:
        if item[1].isdigit():
            item[1] = int(item[1])
        if item[0] == 'Request ID':
            job_status = { 'Files' : [] }
            status[item[1]] = job_status
        section.append(item)
        if item[0] == "VOName": # Last item of Global block
            job_status["Global"] = dict(section)
            section = []
        elif item[0] == "Waiting": # Last item of Summary block
            job_status["Summary"] = dict(section)
            section = []
        elif item[0] == "Duration": # Last item of each file
            f = dict(section)
            job_status["Files"].append(f)
            section = []
    
    if len(status) == 1:
        return status.popitem()[1]
    
    return status
